extern crate common_lib;

use std::env;
use crate::vo;
use common_lib::enums::*;
use common_lib::ErrorCode::CodeRpcInternalError;
use rust_decimal::Decimal;
use sqlx::mysql::MySqlPoolOptions;
use sqlx::{MySql, Pool};
use std::error::Error;
use std::path::Path;
use std::time::{SystemTime, UNIX_EPOCH};
use dotenvy::dotenv;
use log::info;

#[derive(Debug)]
pub struct Database {
    pub pool: Pool<MySql>,
}

#[derive(Debug, sqlx::FromRow)]
pub struct Balance {
    pub id: u32,
    pub user_id: u32,
    pub asset: u16,
    #[sqlx(rename = "type")]
    pub t: u8,
    #[sqlx(rename = "balance")]
    pub value: Decimal,
    pub update_time: f64,
    pub version: u64,
}

impl Database {
    pub async fn new() -> Result<Self, Box<dyn Error>> {
        // dotenv().ok();
        // dotenv::from_filename(".env.local").ok();

        // let env_path = Path::new(".env");
        // if env_path.exists() {
        //     println!(".env file found at: {:?}", env_path.canonicalize().unwrap());
        // } else {
        //     println!(".env file not found!");
        // }
        // let exe_path = env::current_exe().unwrap();
        // let exe_dir = exe_path.parent().unwrap();
        // info!("exec directory: {}", exe_dir.display());
        let database_url = std::env::var("DATABASE_URL").unwrap();
        // match env::var("DATABASE_URL") {
        //     Ok(url) => println!("Database URL: {}", url),
        //     Err(e) => println!("Error: {}", e),
        // }
        // let database_url = env::var("DATABASE_URL").unwrap_or_else(|_| "1".to_string());

        let pool = MySqlPoolOptions::new()
            .max_connections(5)
            .connect(&database_url)
            .await?;

        Ok(Self { pool })
    }

    pub async fn get_balance_by_id(&self, id: i64) -> Result<Option<Balance>, Box<dyn Error>> {
        let user = sqlx::query_as::<MySql, Balance>("select * from balance where id = ?")
            .bind(id)
            .fetch_optional(&self.pool)
            .await?;

        Ok(user)
    }
    pub async fn create_balance(&self, balance: vo::Balance) -> Result<Balance, Box<dyn Error>> {
        let result = sqlx::query(
            r#"
            insert into balance (user_id, asset, type, balance,update_time)
            values (?, ?, ?, ?,?)
            "#,
        )
        .bind(&balance.user_id)
        .bind(&balance.asset)
        .bind(&balance.t)
        .bind(&balance.value)
        .bind(
            SystemTime::now()
                .duration_since(UNIX_EPOCH)
                .unwrap()
                .as_secs_f64(),
        )
        .execute(&self.pool)
        .await?;
        let user = self
            .get_balance_by_id(result.last_insert_id() as i64)
            .await?
            .ok_or_else(|| anyhow::anyhow!("Failed to fetch created user"))?;

        Ok(user)
    }

    pub async fn get_user_balance_list(
        &self,
        user_id: u32,
        assets: Option<&[u32]>,
    ) -> Result<Vec<Balance>, Box<dyn Error>> {
        let query;
        let q;
        if assets.is_none() {
            query = String::from("select * from balance where user_id=?");
            q = sqlx::query_as::<_, Balance>(&query).bind(&user_id);
        } else {
            let in_str = assets
                .unwrap()
                .into_iter()
                .map(|id| id.to_string())
                .collect::<Vec<_>>()
                .join(",");
            query = format!(
                "select * from balance where user_id=? and asset in ({})",
                in_str
            );
            q = sqlx::query_as::<_, Balance>(&query).bind(&user_id);
        }
        let records = q.fetch_all(&self.pool).await?;
        Ok(records)
    }

    pub async fn get_user_balance(
        &self,
        user_id: u32,
        asset: u16,
        t: BalanceType,
    ) -> Result<Option<Balance>, Box<dyn Error>> {
        let query = String::from("select * from balance where user_id=? and asset=? and type=?");
        let q = sqlx::query_as::<_, Balance>(&query)
            .bind(&user_id)
            .bind(&asset)
            .bind(&t);

        let record = q.fetch_optional(&self.pool).await?;
        Ok(record)
    }
    pub async fn update_balance(
        &self,
        user_id: u32,
        asset: u16,
        t: BalanceType,
        change: Decimal,
        version: u64,
    ) -> Result<(), Box<dyn Error>> {
        let sql = format!("update balance set balance=balance+{},version=version+1 where user_id=? and asset=? and type=? and version=?",change);

        let result = sqlx::query(&sql)
            .bind(&user_id)
            .bind(&asset)
            // .bind(&t.code())
            .bind(&t)
            .bind(&version)
            .execute(&self.pool)
            .await?;
        let updated_count = result.rows_affected();
        if updated_count == 0 {
            return Err(CodeRpcInternalError.into());
        }
        Ok(())
    }
}
